package com.lmh.friend.service.exam.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.lmh.common.core.constants.CacheConstants;
import com.lmh.common.core.constants.Constants;
import com.lmh.common.core.domain.TableDataInfo;
import com.lmh.common.core.utils.ThreadLocalUtil;
import com.lmh.friend.domain.exam.dto.ExamQueryDTO;
import com.lmh.friend.domain.exam.dto.ExamRankDTO;
import com.lmh.friend.domain.exam.vo.ExamRankVO;
import com.lmh.friend.domain.exam.vo.ExamVO;
import com.lmh.friend.domain.user.vo.UserVO;
import com.lmh.friend.manager.ExamCacheManager;
import com.lmh.friend.manager.UserCacheManager;
import com.lmh.friend.mapper.exam.ExamMapper;
import com.lmh.friend.mapper.user.UserExamMapper;
import com.lmh.friend.service.exam.IExamService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class ExamServiceImpl implements IExamService {
    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private ExamCacheManager examCacheManager;

    @Autowired
    private UserExamMapper userExamMapper;

    @Autowired
    private UserCacheManager userCacheManager;

    @Override
    public List<ExamVO> list(ExamQueryDTO examQueryDTO) {
        PageHelper.startPage(examQueryDTO.getPageNum(), examQueryDTO.getPageSize());
        return examMapper.selectExamList(examQueryDTO);
    }

    /**
     * redis中会以string类型存储每一个已发布的竞赛的详细信息 e:t:examId，然后当管理员发布竞赛的时候，就将新发布的竞赛存储进redis中
     * 当管理员撤销发布的时候，就在redis中将该竞赛删除，然后未完赛或者历史竞赛的列表信息在redis中就以列表list的形式存储，并且为了节省
     * 内存，我们竞赛列表中存放的都是竞赛的examId，当我们查询未完赛或者历史竞赛列表中的时候，先查到这些examId，然后再查examId对应的竞赛
     * 的详细信息
     * 然后当我们获取未完赛或者历史竞赛列表的时候，首先会先请求打到redis中，如果击中了的话，就根据前端传来的分页信息，获取指定区间的竞赛examId
     * 然后在根据这些examId获取竞赛的详细信息，如果redis中查询的结果为空的话，就说明竞赛列表信息没有同步到redis中，那么这时我们就去MySQL中
     * 查询竞赛信息，然后将从MySQL中查询出来的竞赛的详细信息和竞赛列表信息同步到redis中
     * @param examQueryDTO
     * @return
     */
    @Override
    public TableDataInfo redisList(ExamQueryDTO examQueryDTO) {
        // 如果传来的查询条件有开始时间和结束时间的限制的话，就需要根据查询的是未完赛的列表还是历史竞赛的列表
        // 未完赛：从 e:c:s 未完赛开始时间有序集合中选出开始时间晚于查询条件的集合和 e:c:e 未完赛结束时间集合中结束时间早于查询条件的集合，然后两个集合的交集
        // 历史：从 e:h:s 历史开始时间有序集合中选出开始时间晚于查询条件的集合和 e:h:e 历史结束时间集合中结束时间早于查询条件的集合，然后两个集合的交集



        //从redis当中获取  竞赛列表的数据
        Long total = examCacheManager.getListSize(examQueryDTO.getType(), null);
        List<ExamVO> examVOList = new ArrayList<>();
        // 如果total为空的话，就表示并没有将未完赛的竞赛或者已结束的竞赛信息存储进redis中，
        // 那么这时候就查询MySQL，然后将查询的结果存储进redis中
        if (total == null || total <= 0) {
            // 这个可以直接根据你查询的条件来返回数据，然后这个examVOList就可以作为最终的返回结果
            PageHelper.startPage(examQueryDTO.getPageNum(), examQueryDTO.getPageSize());
            examVOList = list(examQueryDTO);
            // 这个refreshCache方法刷新缓存的时候不会根据你的查询条件进行刷新，而是将所有的未完赛和历史竞赛都刷进缓存中
            examCacheManager.refreshCache(examQueryDTO.getType(), null);
            total = new PageInfo<>(examVOList).getTotal();
        } else {
            TableDataInfo tableDataInfo = examCacheManager.getExamVOList(examQueryDTO, null);
            assembleExamVOList((List<ExamVO>) tableDataInfo.getRows());
            return tableDataInfo;
        }
        if (CollectionUtil.isEmpty(examVOList)) {
            return TableDataInfo.empty();
        }
        assembleExamVOList(examVOList);
        return TableDataInfo.success(examVOList, total);
    }

    @Override
    public String getExamFirstQuestion(Long examId) {
        // 先从redis缓存中查找竞赛的题目列表
        checkAndRefresh(examId);
        return examCacheManager.getExamFirstQuestion(examId).toString();
    }

    private void checkAndRefresh(Long examId) {
        Long listSize = examCacheManager.getExamQuestionListSize(examId);
        if (listSize == null || listSize <= 0) {
            examCacheManager.refreshExamQuestionCache(examId);
        }
    }

    @Override
    public String preQuestion(Long examId, Long questionId) {
        checkAndRefresh(examId);
        return examCacheManager.preQuestion(examId,questionId).toString();
    }

    @Override
    public String nextQuestion(Long examId, Long questionId) {
        checkAndRefresh(examId);
        return examCacheManager.nextQuestion(examId,questionId).toString();
    }

    @Override
    public TableDataInfo examRankList(ExamRankDTO examRankDTO) {
        // 先从缓存中获取竞赛排名信息
        Long total = examCacheManager.getRankListSize(examRankDTO.getExamId());
        List<ExamRankVO> examRankVOList = new ArrayList<>();
        if (total == null || total <= 0) {
            // 如果缓存中不存在竞赛排名信息，那么就从数据库中获取竞赛排名信息并且刷新缓存
            // 先从数据库中找到结果
            PageHelper.startPage(examRankDTO.getPageNum(),examRankDTO.getPageSize());
            examRankVOList = userExamMapper.selectExamRankList(examRankDTO.getExamId());
            // 刷新数据库
            examCacheManager.refreshExamRankCache(examRankDTO.getExamId());
            total = new PageInfo<>(examRankVOList).getTotal();
        }else {
            examRankVOList = examCacheManager.getExamRankList(examRankDTO);
        }
        assembleExamRankVOList(examRankVOList);
        return TableDataInfo.success(examRankVOList, total);
    }

    private void assembleExamRankVOList(List<ExamRankVO> examRankVOList) {
        if (CollectionUtil.isEmpty(examRankVOList)) {
            return;
        }
        for (ExamRankVO examRankVO : examRankVOList) {
            Long userId = examRankVO.getUserId();
            UserVO userVO = userCacheManager.getUserById(userId);
            examRankVO.setNickName(userVO.getNickName());
        }
    }

    private void assembleExamVOList(List<ExamVO> examVOList) {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        List<Long> userExamIdList = examCacheManager.getAllUserExamList(userId);
        if (CollectionUtil.isEmpty(userExamIdList)) {
            return;
        }
        for (ExamVO examVO : examVOList) {
            if (userExamIdList.contains(examVO.getExamId())) {
                examVO.setEnter(true);
            }
        }
    }
}
